/**
 * @author Vitor Franco do Carmo (vitor.carmo@capgemini.com)
 * @since 08/04/2013
 *
 */
package br.com.vfcfw.controller.handler;

import java.sql.SQLRecoverableException;
import java.util.Iterator;

import javax.faces.FacesException;
import javax.faces.application.ProjectStage;
import javax.faces.application.ViewExpiredException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.hibernate.exception.JDBCConnectionException;

import br.com.vfcfw.commons.exception.ApplicationException;
import br.com.vfcfw.commons.exception.BusinessException;
import br.com.vfcfw.commons.util.ConstantesArquitetura;
import br.com.vfcfw.commons.util.ExceptionUtil;
import br.com.vfcfw.commons.util.LogUtil;
import br.com.vfcfw.commons.util.MensagemUtil;
import br.com.vfcfw.controller.listener.SecurityPhaseListener;
import br.com.vfcfw.controller.util.JSFUtil;

/**
 * Implementação de {@link ExceptionHandler} para tratamento genérico das exceções da aplicação.
 * 
 * @author Vitor Franco do Carmo (vitor.carmo@capgemini.com)
 * @since 08/04/2013
 * 
 */
public class ApplicationExceptionHandler extends ExceptionHandlerWrapper {

    private Logger logger = Logger.getLogger(ApplicationExceptionHandler.class);

    private ExceptionHandler wrapped;

    /**
     * Construtor da classe.
     * 
     * @param wrapped {@link ExceptionHandler}.
     */
    public ApplicationExceptionHandler(ExceptionHandler wrapped) {

        super();
        this.wrapped = wrapped;
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.faces.context.ExceptionHandlerWrapper#getWrapped()
     */
    @Override
    public ExceptionHandler getWrapped() {

        return this.wrapped;
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.faces.context.ExceptionHandlerWrapper#handle()
     */
    @Override
    public void handle() throws FacesException {

        FacesContext facesContext = JSFUtil.getFacesContext();

        ETipoErro tipoErro = null;

        if (facesContext.isProjectStage(ProjectStage.Development)) {

            getWrapped().handle();

        } else {

            for (Iterator<ExceptionQueuedEvent> iteratorException = getUnhandledExceptionQueuedEvents().iterator(); iteratorException
                .hasNext();) {

                ExceptionQueuedEvent event = iteratorException.next();
                ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();

                Throwable exception = ExceptionUtil.obterBusinessException(context.getException());

                if (exception instanceof FacesException) {

                    exception = (exception.getCause() != null) ? exception.getCause() : exception;
                }

                try {
                    tipoErro = tratarExcecao(exception);
                } finally {

                    iteratorException.remove();
                }

                exibirErro(facesContext, tipoErro);

                break;
            }
        }
    }

    private ETipoErro tratarExcecao(Throwable exception) {

        if (exception instanceof AbortProcessingException) {
            LogUtil.error(logger, MensagemUtil.obterMensagem(ConstantesArquitetura.MSG_ERRO_EVENT_LISTENER), exception);

            JSFUtil.getSessionMap().put(ConstantesArquitetura.CHAVE_DETALHE_ERRO_EXCECAO,
                exception.getLocalizedMessage());

            return ETipoErro.ERRO_EVENT_LISTENER;
        } else if (exception instanceof ViewExpiredException) {
            ViewExpiredException viewExpiredException = (ViewExpiredException) exception;

            if (LogUtil.isDebugHabilitado(logger)) {
                LogUtil.debug(
                    logger,
                    viewExpiredException,
                    MensagemUtil.obterMensagem(ConstantesArquitetura.MSG_ERRO_VIEW_EXPIRADA,
                        viewExpiredException.getViewId()));

            }

            HttpSession session = (HttpSession) JSFUtil.getExternalContext().getSession(false);

            if (session != null) {
                session.invalidate();
            }

            return ETipoErro.ERRO_VIEW_EXPIRADA;
        } else if (exception instanceof BusinessException) {
            BusinessException businessException = (BusinessException) exception;

            for (String msgErro : businessException.getListaErros()) {

                JSFUtil.adicionarMensagemErro(msgErro);
            }
            return ETipoErro.ERRO_NEGOCIO;
        } else if (exception instanceof ApplicationException) {
            LogUtil.error(logger, exception);

            JSFUtil.getSessionMap().put(ConstantesArquitetura.CHAVE_DETALHE_ERRO_EXCECAO,
                exception.getLocalizedMessage());

            return ETipoErro.ERRO_INTERNO_SISTEMA;
        } else if (exception instanceof JDBCConnectionException) {
            LogUtil.error(logger, exception);

            JSFUtil.getSessionMap().put(ConstantesArquitetura.CHAVE_DETALHE_ERRO_EXCECAO,
                exception.getLocalizedMessage());

            return ETipoErro.ERRO_CONEXAO;
        } else if (exception instanceof SQLRecoverableException) {
            LogUtil.error(logger, exception);

            JSFUtil.getSessionMap().put(ConstantesArquitetura.CHAVE_DETALHE_ERRO_EXCECAO,
                exception.getLocalizedMessage());

            return ETipoErro.ERRO_CONEXAO;
        } else {
            LogUtil.error(logger, exception);

            JSFUtil.getSessionMap().put(ConstantesArquitetura.CHAVE_DETALHE_ERRO_EXCECAO,
                exception.getLocalizedMessage());

            return ETipoErro.ERRO_INTERNO_SISTEMA;
        }

    }

    /**
     * Método responsável por exibir o erro para o usuário.
     * 
     * @param facesContext {@link FacesContext}.
     * @param tipoErro {@link ETipoErro}.
     */
    private void exibirErro(FacesContext facesContext, ETipoErro tipoErro) {

        if (tipoErro.isRedirecionar()) {

            SecurityPhaseListener securityPhaseListener = new SecurityPhaseListener();
            securityPhaseListener.doRedirect(facesContext, tipoErro.getPagina());
        }
    }

    /**
     * Enumeração com os tipos de erro da {@link ApplicationExceptionHandler}.
     * 
     * @author Vitor Franco do Carmo (vitor.carmo@capgemini.com)
     * @since 17/04/2013
     * 
     */
    private enum ETipoErro {
        ERRO_NEGOCIO(false, null),
        ERRO_CONEXAO(true, "/pages/erro_conexao.jsf"),
        ERRO_VIEW_EXPIRADA(true, "/pages/login.jsf"),
        ERRO_INTERNO_SISTEMA(true, "/pages/error.jsf?statusCode=msg.erro.erro_interno_sistema"),
        ERRO_EVENT_LISTENER(true, "/pages/error.jsf?statusCode=msg.erro.event.listener");

        private boolean redirecionar;

        private String pagina;

        /**
         * Construtor da classe.
         * 
         * @param redirecionar <tt>TRUE</tt> redirecionará para a página informada, <tt>FALSE</tt> não redirecionará.
         * @param pagina Página para a qual será redirecionada.
         */
        private ETipoErro(boolean redirecionar, String pagina) {

            this.redirecionar = redirecionar;
            this.pagina = pagina;
        }

        /**
         * Verifica se deverá ser redirecionado para outra página.
         * 
         * @return <tt>TRUE</tt> vai redirecionar para outra página, <tt>FALSE</tt> caso contrário.
         */
        public boolean isRedirecionar() {

            return redirecionar;
        }

        /**
         * Retorna a página para a qual será redirecionado.
         * 
         * @return Página.
         */
        public String getPagina() {

            return pagina;
        }

    }

}
